﻿using System;
using System.Data.SqlClient;
using PropertyExpression.Common;

namespace PropertyExpression.ControllableQuery
{
    public class QueryContext
    {
        private readonly SqlCommand sqlCommand;
        private readonly SqlParameterNameGenerator sqlParameterNameGenerator = new SqlParameterNameGenerator();

        public QueryContext(SqlCommand sqlCommand)
        {
            this.sqlCommand = sqlCommand;
        }

        public IParam<T> CreateParam<T>(Func<string, SqlParameter> sqlParameterFunc, Func<T, object> toSqlParameterValueFunc)
        {
            return new Param<T>(this, sqlParameterFunc, toSqlParameterValueFunc);
        }

        private class Param<T> : IParam<T>
        {
            private readonly QueryContext parent;
            private readonly Lazy<Action> onToSql;
            private IOptionalValue<string> name = OptionalValue.Nothing<string>();
            private IOptionalValue<T> value = OptionalValue.Nothing<T>();

            public Param(QueryContext parent, Func<string, SqlParameter> sqlParameterFunc, Func<T, object> toSqlParameterValueFunc)
            {
                this.parent = parent;

                onToSql = new Lazy<Action>(
                    () =>
                        {
                            var sqlParameter = sqlParameterFunc(GetName());
                            value.ProcessValue(_ => sqlParameter.Value = toSqlParameterValueFunc(_));
                            ValueChanged += _ => sqlParameter.Value = toSqlParameterValueFunc(_);
                            NameChanged += _ => sqlParameter.ParameterName = _;
                            parent.sqlCommand.Parameters.Add(sqlParameter);
                            return () => { };
                        }
                    );
            }

            public string Name
            {
                set
                {
                    name = value.AsOptionalValue();
                    if (NameChanged != null) NameChanged(value);
                }
            }

            private event Action<string> NameChanged;

            public T Value
            {
                set
                {
                    this.value = value.AsOptionalValue();
                    if (ValueChanged != null) ValueChanged(value);
                }
            }

            private event Action<T> ValueChanged;

            public string ToSql()
            {
                onToSql.Value();
                return GetName();
            }

            public override string ToString()
            {
                return ToSql();
            }

            private string GetName()
            {
                if (!name.HasValue())
                {
                    name = parent.sqlParameterNameGenerator.GetNextName().AsOptionalValue();
                }
                return name.GetValue();
            }
        }
    }

}